﻿using System;
using System.Linq.Expressions;
using System.Reflection;

namespace YSUtils.Reflection
{
	/// <summary>
	/// Allows getting property name.
	/// </summary>
	/// <remarks>
	/// The original idea: 
	/// <a href="http://ivanz.com/2009/12/04/how-to-avoid-passing-property-names-as-strings-using-c-3-0-expression-trees/">Ivan Zlatev.</a>
	/// </remarks>
	/// <example>
	/// Getting the public non-static property name.
	/// <code>
	/// var testProperties = new ClassWithProperties();
	/// string propertyName = testProperties.GetPropertyName(x => x.PropertyOne);
	/// </code>
	/// or
	/// <code>
	/// string propertyName = PropertyUtil.GetPropertyName&lt;ClassWithProperties&gt;(x => x.PropertyOne);
	/// </code>
	/// The variable <i>propertyName</i> will have the value "PropertyOne".
	/// <para/>
	/// Getting the public static property name.
	/// <code>
	/// string propertyName = PropertyUtil.GetPropertyName(() => ClassWithStaticProperties.PropertyTwo);
	/// </code>
	/// The variable <i>propertyName</i> will have the value "PropertyTwo".
	/// </example>
    public static class PropertyUtil
    {
        /// <summary>
        /// Gets the property name by property reference.
        /// May be used with non-static properties.
        /// </summary>
        /// <typeparam name="TObject">Specifies a class with properties.</typeparam>
        /// <param name="type">The class type.</param>
        /// <param name="propertyRefExpr">A property reference.</param>
        /// <returns>The property name.</returns>
        public static string GetPropertyName<TObject>(this TObject type,
                                                       Expression<Func<TObject, object>> propertyRefExpr)
        {
            return GetPropertyNameCore(propertyRefExpr.Body);
        }

        /// <summary>
        /// Gets the property name by property reference.
        /// May be used with static properties.
        /// </summary>
        /// <typeparam name="TObject">Specifies a class with properties.</typeparam>
        /// <param name="propertyRefExpr">A property reference.</param>
        /// <returns>The property name.</returns>
        public static string GetPropertyName<TObject>(Expression<Func<TObject, object>> propertyRefExpr)
        {
            return GetPropertyNameCore(propertyRefExpr.Body);
        }

        /// <summary>
        /// Gets the property name by property reference.
        /// May be used with static properties.
        /// </summary>
        /// <param name="propertyRefExpr">A property reference.</param>
        /// <returns>The property name.</returns>
        public static string GetPropertyName(Expression<Func<object>> propertyRefExpr)
        {
            return GetPropertyNameCore(propertyRefExpr.Body);
        }

        /// <summary>
        /// Gets the property name.
        /// </summary>
        /// <param name="propertyRefExpr">A property reference.</param>
        /// <returns>The property name.</returns>
        private static string GetPropertyNameCore(Expression propertyRefExpr)
        {
            if (propertyRefExpr == null)
                throw new ArgumentNullException("propertyRefExpr", "propertyRefExpr is null.");

            MemberExpression memberExpr = propertyRefExpr as MemberExpression;
            if (memberExpr == null)
            {
                UnaryExpression unaryExpr = propertyRefExpr as UnaryExpression;
                if (unaryExpr != null && unaryExpr.NodeType == ExpressionType.Convert)
                    memberExpr = unaryExpr.Operand as MemberExpression;
            }

            if (memberExpr != null && memberExpr.Member.MemberType == MemberTypes.Property)
                return memberExpr.Member.Name;

            throw new ArgumentException("No property reference expression was found.",
                             "propertyRefExpr");
        }
    }

}